﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using VSDBM.Code.dao;
using VSDBM.Code.dto;
using VSDBM.Code.functions;
using System.Text;


/// <summary>
/// Summary description for SmithWatermanMMF
/// </summary>
public class SmithWatermanMMF
{
    public float penaltyGapExist { get; set; }
    public float penaltyGapExtend { get; set; }
    public String databaseSequence { get; set; }
    public String querrySequence { get; set; }
    public SubstitutionMatrix substitutionMatrix { get; set; }
    public float[] DatabaseScoreArray;
    public float[] QuerryScoreArray;
    public float highScore = 0;
    public int endAlignmentDatabase = 0;
    public int endAlignmentQuery = 0;
    public int startAlignmentDatabase = 0;
    public int startAlignmentQuery = 0;
    public PairwiseAlignmentDB alignment;
    public MemoryMappedMatrix matrix;
    public Boolean shouldTraceback;


    public SmithWatermanMMF(Sequence database, Sequence querry, SubstitutionMatrix substitutionMatrix, Boolean shoudTraceback)
	{
        if (database.sequence.Length < 1 || querry.sequence.Length < 1) throw new Exception("Error in Smith-Waterman-Gotoh dynamic programming. Length of database sequence or query sequence has to be at least 1 bp");


        this.penaltyGapExist = substitutionMatrix.gapExistPenalty;
        this.penaltyGapExtend = substitutionMatrix.gapExtendPenalty;
        this.databaseSequence = database.sequence;
        this.querrySequence = querry.sequence;
        this.substitutionMatrix = substitutionMatrix;
        this.shouldTraceback = shoudTraceback;


        //test other matrixes if u DARE!.. =D(well.. i did).

        if (shouldTraceback)
        {
            createMatrix();
        }
        else
        {
            createMatrixWOTraceBack();
        }
	}

    protected float Max(float first, float second)
    {
        if (first > second) return first;
        else return second;
    }

    public void createMatrixWOTraceBack()
    {
        //CODE FOR LAST_MOVE:
        // 1 - Diagonal
        // 2 - Insertion
        // 3 - Deletion
        // 4 - None

        
        //initialize the Gotoh arrays - containing the score of the best previous deletion
        this.DatabaseScoreArray = new float[databaseSequence.Length + 1];
        this.QuerryScoreArray = new float[querrySequence.Length + 1];

        
        StringBuilder firstlinebuilder = new StringBuilder();
        //First set all values in the horizontal (database) to zero(first line)
        for (int i = 0; i < databaseSequence.Length + 1; i++)
        {
            //  matrix.setPosition(i, 0,clearData);
            DatabaseScoreArray[i] = 0 - penaltyGapExist;
        }
        long[] lastline = new long[querrySequence.Length + 1];
        //Second set all values in the vertical (query) to zero(first column)
        for (int j = 0; j < querrySequence.Length + 1; j++)
        {
            QuerryScoreArray[j] = 0 - penaltyGapExist;
            lastline[j] = 0;
        }
        
        
        //Go down use dimension of the query
        for (int i = 1; i < databaseSequence.Length + 1; i++)
        {

            float lastscore = 0;
            long[] thisline = new long[querrySequence.Length + 1];
            thisline[0] = 0;

            for (int j = 1; j < querrySequence.Length + 1; j++)
            {

                //i=database sequence in the horizontal
                //j=query sequence in the vertical


                //the database sequence is in the horizontal, the query in the vertical axis of the matrix
                //Diagonal score is the previous score and in addition the similarityValue;
                //float scoredi = (float)Convert.ToDouble(matrix.getPosition(i - 1, j - 1).Split(',')[0]);
                float scoreDiagonal = lastline[j - 1] + substitutionMatrix.GetSimilarityValue(databaseSequence[i - 1], querrySequence[j - 1]);

                //Find the highest scoring insertion, testing all matrix to the upper side;
                float downScoreInsertion = 0;
                downScoreInsertion = this.Max(lastscore - penaltyGapExist, DatabaseScoreArray[i] - penaltyGapExtend);

                DatabaseScoreArray[i] = downScoreInsertion;


                //Find the highest scroing deletion, testing all matrix entries to the left side
                // float scorede = (float)Convert.ToDouble(matrix.getPosition(i - 1, j).Split(',')[0]);
                float rightScoreDeletion = 0;
                rightScoreDeletion = Math.Max(lastline[j] - penaltyGapExist, QuerryScoreArray[j] - penaltyGapExtend);
                QuerryScoreArray[j] = rightScoreDeletion;

                Object[] nowcell = GetMaximumPosition(scoreDiagonal, downScoreInsertion, rightScoreDeletion, 0);
                float nowScore = (float)nowcell[0];
                int nowLastMove = (int)nowcell[1];


                String scorenow = nowScore.ToString() + "," + nowLastMove.ToString();
                while (scorenow.Length < ((querrySequence.Length * substitutionMatrix.highestScore).ToString().Length) + 2)
                {
                    scorenow += "X";
                }

                lastscore = nowScore;
                thisline[j] = (long)nowScore;
              



                //Updating the highest scoring matrix entry
                if (nowScore > highScore)
                {
                    //new highscore
                    highScore = nowScore;
                    endAlignmentDatabase = i;
                    endAlignmentQuery = j;
                }



            }

           
            lastline = thisline;
        }
        
        //the end.
        
    }


    public void createMatrix()
    {
        //CODE FOR LAST_MOVE:
        // 1 - Diagonal
        // 2 - Insertion
        // 3 - Deletion
        // 4 - None

        //initialize the matrix
        this.matrix = new MemoryMappedMatrix(((querrySequence.Length * substitutionMatrix.highestScore).ToString().Length) + 2, databaseSequence.Length + 1, querrySequence.Length + 1, AppDomain.CurrentDomain.BaseDirectory);

        //initialize the Gotoh arrays - containing the score of the best previous deletion
        this.DatabaseScoreArray = new float[databaseSequence.Length + 1];
        this.QuerryScoreArray = new float[querrySequence.Length + 1];

        String clearData = "0,4";
        while (clearData.Length < ((querrySequence.Length * substitutionMatrix.highestScore).ToString().Length) + 2)
        {
            clearData += "X";
        }
        StringBuilder firstlinebuilder = new StringBuilder();
        //First set all values in the horizontal (database) to zero(first line)
        for (int i = 0; i < databaseSequence.Length+1; i++)
        {
          //  matrix.setPosition(i, 0,clearData);
            DatabaseScoreArray[i] = 0 - penaltyGapExist;
        }
        long[] lastline = new long[querrySequence.Length + 1];
        //Second set all values in the vertical (query) to zero(first column)
        for (int j = 0; j < querrySequence.Length + 1; j++)
       {
            firstlinebuilder.Append(clearData);
            QuerryScoreArray[j] = 0 - penaltyGapExist;
            lastline[j] = 0;
        }
        matrix.setLineByOffset(0, 0, firstlinebuilder.ToString());

        StringBuilder lineBuilder = new StringBuilder();
        //Go down use dimension of the query
        for (int i = 1; i < databaseSequence.Length+1; i++)
        {
            
            float lastscore = 0;
            long[] thisline = new long[querrySequence.Length + 1];
            thisline[0] = 0;
            
            for (int j = 1; j < querrySequence.Length+1; j++)
            {
                
                //i=database sequence in the horizontal
                //j=query sequence in the vertical

                
                //the database sequence is in the horizontal, the query in the vertical axis of the matrix
                //Diagonal score is the previous score and in addition the similarityValue;
                //float scoredi = (float)Convert.ToDouble(matrix.getPosition(i - 1, j - 1).Split(',')[0]);
                float scoreDiagonal = lastline[j-1] + substitutionMatrix.GetSimilarityValue(databaseSequence[i - 1], querrySequence[j - 1]);

                //Find the highest scoring insertion, testing all matrix to the upper side;
                float downScoreInsertion = 0;
                downScoreInsertion = this.Max(lastscore - penaltyGapExist, DatabaseScoreArray[i] - penaltyGapExtend);

                DatabaseScoreArray[i] = downScoreInsertion;


                //Find the highest scroing deletion, testing all matrix entries to the left side
               // float scorede = (float)Convert.ToDouble(matrix.getPosition(i - 1, j).Split(',')[0]);
                float rightScoreDeletion = 0;
                rightScoreDeletion = Math.Max(lastline[j] - penaltyGapExist, QuerryScoreArray[j] - penaltyGapExtend);
                QuerryScoreArray[j] = rightScoreDeletion;

                Object[] nowcell = GetMaximumPosition(scoreDiagonal, downScoreInsertion, rightScoreDeletion, 0);
                float nowScore = (float)nowcell[0];
                int nowLastMove = (int)nowcell[1];


                String scorenow = nowScore.ToString() + "," + nowLastMove.ToString();
                while (scorenow.Length < ((querrySequence.Length * substitutionMatrix.highestScore).ToString().Length) + 2)
                {
                    scorenow += "X";
                }
                
                lastscore = nowScore;
                thisline[j] = (long)nowScore;
                lineBuilder.Append(scorenow);



                //Updating the highest scoring matrix entry
                if (nowScore > highScore)
                {
                    //new highscore
                    highScore = nowScore;
                    endAlignmentDatabase = i;
                    endAlignmentQuery = j;
                }



            }

           matrix.setLineByOffset(i,1, lineBuilder.ToString());
           lineBuilder.Clear();
            lastline = thisline;
        }
        if (shouldTraceback)
        {
            Traceback();
        }
        else
        {
            matrix.DeleteFile();
        }
    }

    public void Traceback()
    {

        //Actual position in the matrix; D..database; Q..query
        int posD = endAlignmentDatabase;
        int posQ = endAlignmentQuery;
        StringBuilder databaseBuilder = new StringBuilder();
        StringBuilder queryBuilder = new StringBuilder();
        int LastMove = Convert.ToInt32(matrix.getPosition(posD, posQ).Split(',')[1].Split('X')[0]);
        float insScoreA = 0;
        float insScoreB = 0;
        float delScoreA = 0;
        float delScoreB = 0;
        while (LastMove != 4)
        {

            //Move to the next character
            switch (LastMove)
            {
                case 1: //Diagonal
                    databaseBuilder.Append(databaseSequence[posD - 1]);
                    queryBuilder.Append(querrySequence[posQ - 1]);
                    posD--; posQ--;

                    break;
                case 3://Deletion
                    int startPosD = posD;
                    delScoreA = (float)Convert.ToDouble(matrix.getPosition(startPosD, posQ).Split(',')[0]);
                    do
                    {
                        databaseBuilder.Append(databaseSequence[posD - 1]);
                        queryBuilder.Append('-');
                        posD--;
                        delScoreB = (float)Convert.ToDouble(matrix.getPosition(posD, posQ).Split(',')[0]);

                    }
                    while (Math.Round(delScoreB, 4) != Math.Round((delScoreA + penaltyGapExist + penaltyGapExtend * (startPosD - posD - 1)), 4));

                    break;
                case 2://Insertion
                    int startPosQ = posQ;
                    insScoreA = (float)Convert.ToDouble(matrix.getPosition(posD, startPosQ).Split(',')[0]);
                    do
                    {
                        databaseBuilder.Append('-');
                        queryBuilder.Append(querrySequence[posQ - 1]);
                        posQ--;
                        insScoreB = (float)Convert.ToDouble(matrix.getPosition(posD, posQ).Split(',')[0]);

                    }
                    while (Math.Round(insScoreB, 2) != Math.Round((insScoreA + penaltyGapExist + penaltyGapExtend * (startPosQ - posQ - 1)), 2));
                    
                    break;
                default: throw new InvalidOperationException("none of the last move codes was achived");
                    
            }

            LastMove = Convert.ToInt32(matrix.getPosition(posD, posQ).Split(',')[1].Split('X')[0]);
        }
        if (endAlignmentDatabase > 0 && endAlignmentQuery > 0)
        {
            startAlignmentDatabase = posD + 1;
            startAlignmentQuery = posQ + 1;
            alignment = new PairwiseAlignmentDB(databaseBuilder.ToString(), queryBuilder.ToString());
        }

        matrix.DeleteFile();

    }

    protected static Object[] GetMaximumPosition(float scoreDiagonal, float scoreInsertion, float scoreDeletion, float scoreNone)
    {
        Object[] cell = new Object[2];

        if (scoreDiagonal > scoreNone)
        {
            //exclude scoreNone

            if (scoreDiagonal >= scoreInsertion)
            {
                //exclude scoreNone & scoreInsertion

                if (scoreDiagonal >= scoreDeletion)
                {
                    //exclude scoreNone & scoreInsertion & scoreDeletion => DIAGONAL

                    cell[0] = scoreDiagonal;
                    cell[1] = 1;
                }
                else
                {
                    //exclude scoreNone & scoreInsertion & scoreDiagonal => DELETION
                    cell[0] = scoreDeletion;
                    cell[1] = 3;

                }
            }
            else
            {
                //exclude scoreNone & scoreDiagonal


                if (scoreInsertion > scoreDeletion)
                {
                    //exclude scoreNone & scoreDiagonal & scoreDeletion => INSERTION
                    cell[0] = scoreInsertion;
                    cell[1] = 2;
                }
                else
                {
                    //exclude scoreNone &scoreDiagonal & scoreInsertion => DELETION
                    cell[0] = scoreDeletion;
                    cell[1] = 3;


                }
            }
        }
        else
        {
            //exclude scoreDiagonal
            if (scoreInsertion > scoreNone)
            {
                //exclude scoreDiagonal & scoreNone

                if (scoreInsertion > scoreDeletion)
                {
                    //exclude scoreDiagonal & scoreNone & scoreDeletion => INSERTION
                    cell[0] = scoreInsertion;
                    cell[1] = 2;
                }
                else
                {
                    //exclude scoreDiagonal & scoreNone & scoreInsertion => DELETION
                    cell[0] = scoreDeletion;
                    cell[1] = 3;
                }
            }
            else
            {
                //exclude scoreDiagonal & scoreInsertion
                if (scoreDeletion > scoreNone)
                {
                    //exclude scoreDiagonal & scoreInsertion & scoreNone => DELETION
                    cell[0] = scoreDeletion;
                    cell[1] = 3;
                }
                else
                {
                    //exclude scoreDiagonal & scoreInsertion & scoreDeletion =>NONE
                    cell[0] = scoreNone;
                    cell[1] = 4;
                }
            }


        }


        return cell; //UFA!!
    }

}